//go:build windows
// +build windows

// Package dll类 备注, 由go的syscall包复制过来修改的.
package 内部类

import (
	"fmt"
	"gitee.com/go_888/internal-call/win"
	"strings"

	"sync"
	"syscall"
	"unicode/utf16"
	"unsafe"
)

var (
	user32                   = DLL载入("user32.dll")
	getWindowThreadProcessId = user32.X命令("GetWindowThreadProcessId")
	getWindowTextLengthW     = user32.X命令("GetWindowTextLengthW")
	getWindowTextW           = user32.X命令("GetWindowTextW")
	getClassNameW            = user32.X命令("GetClassNameW")
	enumWindows              = user32.X命令("EnumWindows")
	findWindowExW            = user32.X命令("FindWindowExW")
)

type X窗口坐标 struct {
	X左, X顶, X右, X下 int32
}
type X窗口坐标大小 struct {
	X左, X顶, X宽, X高 int32
}

// 定义窗口信息结构体
type X窗口信息 struct {
	X窗口句柄 int
	X进程id int
	X线程id int
	X标题   string
	X类名   string
}

// X枚举顶级窗口 用于枚举当前系统中的所有窗口信息。
// 该函数通过系统调用获取每个窗口的句柄，并为每个窗口创建信息对象，
// 包含窗口的句柄、标题、类名、进程ID和线程ID。
func X枚举顶级窗口() []X窗口信息 {
	// windows 用于存储所有窗口的信息。
	var windows []X窗口信息
	// mu 用于确保并发访问 windows 时的数据安全。
	var mu sync.Mutex

	// 创建枚举窗口的回调函数
	callback := func(hwnd uintptr, lParam uintptr) uintptr {
		mu.Lock()
		defer mu.Unlock()

		// 将窗口句柄添加到 windows 切片中
		windows = append(windows, X窗口信息{
			X窗口句柄: int(hwnd),
		})
		return 1
	}

	// 调用 enumWindows 函数开始枚举窗口
	_, _, _ = enumWindows.X调用(uintptr(unsafe.Pointer(syscall.NewCallback(callback))), 0)

	// 使用 goroutines 处理每个窗口的信息
	var wg sync.WaitGroup
	for _, info := range windows {
		wg.Add(1)
		go func(info X窗口信息) {
			defer wg.Done()
			mu.Lock()
			defer mu.Unlock()

			// 获取窗口标题长度
			length, _, _ := getWindowTextLengthW.X调用(uintptr(info.X窗口句柄))
			if length == 0 {
				return
			}

			// 分配缓冲区用于存储标题
			buffer := make([]uint16, length+1)
			_, _, _ = getWindowTextW.X调用(uintptr(info.X窗口句柄), uintptr(unsafe.Pointer(&buffer[0])), uintptr(length+1))

			// 获取窗口类名
			classNameBuffer := make([]uint16, 256)
			_, _, _ = getClassNameW.X调用(uintptr(info.X窗口句柄), uintptr(unsafe.Pointer(&classNameBuffer[0])), 256)

			// 获取进程 ID 和线程 ID
			var processID uintptr
			_, _, _ = getWindowThreadProcessId.X调用(uintptr(info.X窗口句柄), uintptr(unsafe.Pointer(&processID)))
			threadID := processID & 0xFFFF // 取低16位作为线程ID

			// 更新窗口信息
			info.X标题 = syscall.UTF16ToString(buffer)
			info.X类名 = syscall.UTF16ToString(classNameBuffer)
			info.X进程id = int(processID)
			info.X线程id = int(threadID)

			// 更新 windows 切片中的信息
			for i, w := range windows {
				if w.X窗口句柄 == info.X窗口句柄 {
					windows[i] = info
					break
				}
			}
		}(info)
	}

	// 等待所有 goroutines 完成
	wg.Wait()

	// 返回所有窗口的信息
	return windows
}

// X枚举子孙窗口 枚举指定父窗口的所有子窗口。
// 参数:
// 父窗口句柄: 父窗口的句柄。
// 类名筛选: 子窗口类名的字符串。
// 返回值:
// 返回一个包含所有子窗口句柄的切片。如果枚举失败，返回nil。
func X枚举子孙窗口(窗口句柄 int, 类名筛选 ...string) []int {
	// 存储所有符合条件的子窗口句柄
	var handles []int
	win.EnumChildWindows(win.HWND(窗口句柄), syscall.NewCallback(func(hWnd uintptr, lParam uintptr) uintptr {
		if len(类名筛选) > 0 && 类名筛选[0] != "" {
			if strings.ToLower(类名筛选[0]) == strings.ToLower(X取类名(int(hWnd))) {
				handles = append(handles, int(hWnd))
			}
		} else {
			handles = append(handles, int(hWnd))
		}
		return 1 // 继续枚举
	}), 0)

	// 返回所有符合条件的子窗口句柄
	return handles
}

// X取类名 通过窗口句柄检索并返回窗口类名。
// 窗口句柄: 指定窗口的句柄。
// 返回值: 返回窗口的类名字符串；如果无法获取类名，则返回空字符串。
func X取类名(窗口句柄 int) string {
	// 定义一个缓冲区来存储类名
	className := make([]uint16, 256)

	// 调用 GetClassName 获取类名
	length, _ := win.GetClassName(win.HWND(窗口句柄), &className[0], len(className))

	if length == 0 {
		fmt.Println("获取类名失败")
		return ""
	}

	// 将类名从 UTF-16 转换为字符串
	return syscall.UTF16ToString(className[:length])
}

// X取标题 通过窗口句柄获取窗口的标题。
// 参数:
// 窗口句柄 - 窗口的句柄。
// 返回值:
// 窗口的标题字符串。
func X取标题(窗口句柄 int) string {
	// 定义 HWND 类型，用于窗口句柄。
	type HWND uintptr

	// 定义常量
	const (
		ERROR_SUCCESS = 0 // 手动定义 ERROR_SUCCESS 常量，用于表示成功。
	)

	// 调用 GetWindowTextLengthW 获取窗口标题的长度。
	var dwSize uint32
	r1, _, err := getWindowTextLengthW.X调用(uintptr(窗口句柄))
	if r1 == 0 {
		// 如果返回值为0，检查错误是否为非成功状态。
		if err != nil && err.(syscall.Errno) != ERROR_SUCCESS {
			return ""
		}
		dwSize = 0
	} else {
		dwSize = uint32(r1)
	}

	// 如果标题长度为0，则直接返回空字符串。
	if dwSize == 0 {
		return ""
	}

	// 准备缓冲区，包括空字符，并额外增加一个字节以确保足够的空间。
	dwSize++
	bufferSize := dwSize

	// 创建一个足够大的字节切片。
	szTitle := make([]uint16, bufferSize)

	// 调用 GetWindowTextW 获取窗口标题。
	_, _, err = getWindowTextW.X调用(uintptr(窗口句柄), uintptr(unsafe.Pointer(&szTitle[0])), uintptr(dwSize+1))
	if err != nil && err.(syscall.Errno) != ERROR_SUCCESS {
		return ""
	}

	// 截取实际数据部分，直到第一个 NUL 字符。
	utf16Bytes := szTitle[:dwSize-1]
	utf16Runes := utf16.Decode(utf16Bytes)

	// 将 []rune 转换为 string。
	utf8String := string(utf16Runes)

	return utf8String
}
